package com.whfc.common.file.impl;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.*;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.whfc.common.file.FileHandler;
import com.whfc.common.file.properties.AliyunFileProperties;
import com.whfc.common.file.properties.FileProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 阿里云OSS文件处理
 *
 * @author qzexing
 * @version 1.0
 * @date 2021-03-02 10:35
 */
public class AliyunOssFileHandler implements FileHandler {

    private final static Logger logger = LoggerFactory.getLogger(AliyunOssFileHandler.class);

    /**
     * accessKeyId
     */
    private String accessKeyId;

    /**
     * accessKeySecret
     */
    private String accessKeySecret;

    /**
     * endpoint
     */
    private String endpoint;

    /**
     * bucket
     */
    private String bucket;

    /**
     * 访问域名
     */
    private String host;

    public AliyunOssFileHandler(FileProperties properties) {
        AliyunFileProperties fileProperties = (AliyunFileProperties) properties.getChild();
        accessKeyId = fileProperties.getKey_id();
        accessKeySecret = fileProperties.getKey_secret();
        endpoint = fileProperties.getEndpoint();
        bucket = fileProperties.getBucket();
        host = fileProperties.getSave_url();
    }

    /**
     * 获取oss client
     *
     * @return
     */
    private OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    @Override
    public String upload(String path, InputStream is) {
        Map<String, String> headers = new HashMap<>(0);
        return upload(path, is, headers);
    }

    @Override
    public String upload(String path, InputStream is, String ex) {
        Map<String, String> headers = new HashMap<>(0);
        Map<String, String> tags = new HashMap<>(1);
        tags.put("expires", ex);
        return upload(path, is, headers, tags);
    }

    @Override
    public String upload(String path, InputStream is, Map<String, String> headers) {
        return upload(path, is, headers, Collections.emptyMap());
    }

    @Override
    public String upload(String path, InputStream is, Map<String, String> headers, Map<String, String> tags) {

        //设置header
        ObjectMetadata metadata = new ObjectMetadata();
        if (headers != null && !headers.isEmpty()) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                metadata.setHeader(entry.getKey(), entry.getValue());
            }
        }

        //设置标签
        if (tags != null && !tags.isEmpty()) {
            metadata.setObjectTagging(tags);
        }

        //上传
        OSS ossClient = ossClient();
        PutObjectResult result = ossClient.putObject(bucket, path, is, metadata);
        String url = String.format("%s/%s", host, path);
        logger.info("上传文件结果 MD5={},url={}", result.getETag(), url);
        ossClient.shutdown();
        return url;
    }

    @Override
    public void download(String key, String saveFilePath) {
        OSS ossClient = ossClient();
        ObjectMetadata metadata = ossClient.getObject(new GetObjectRequest(bucket, key), new File(saveFilePath));
        logger.info("下载文件结果 file={},MD5={}", key, metadata.getETag());
        ossClient.shutdown();
    }

    @Override
    public Long getFileSize(String ossKey) {
        OSS ossClient = ossClient();
        ObjectMetadata metadata = ossClient.headObject(new HeadObjectRequest(bucket, ossKey));
        logger.info("获取文件信息 metadata:{}", metadata);
        ossClient.shutdown();
        long length = 0;
        if (metadata != null) {
            length = metadata.getContentLength();
        }
        return length;
    }

    @Override
    public void delete(List<String> fileUrls) {
        List<String> ossKeys = fileUrls.stream().filter(url -> !"".equals(url))
                .map(this::getPath)
                .distinct()
                .collect(Collectors.toList());
        DeleteObjectsRequest request = new DeleteObjectsRequest(bucket);
        request.setKeys(ossKeys);
        OSS ossClient = ossClient();
        DeleteObjectsResult result = ossClient.deleteObjects(request);
        logger.info("文件删除结果,entity:{}", result.getDeletedObjects());
        ossClient.shutdown();
    }

    @Override
    public void delete(String url) {
        OSS ossClient = ossClient();
        ossClient.deleteObject(bucket, this.getPath(url));
        ossClient.shutdown();
    }

    @Override
    public boolean checkFile(String path) {
        OSS ossClient = ossClient();
        boolean found = ossClient.doesObjectExist(bucket, path);
        ossClient.shutdown();
        logger.info("文件{}是否存在|{}", path, found);
        return found;
    }

    @Override
    public Map<String, String> getPolicy(String path, String callbackUrl) {

        //policy 过期时间为 30分钟
        long expireTime = 1800;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);
        PolicyConditions policyConditions = new PolicyConditions();
        policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 5368709120L);
        policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path);

        OSS ossClient = ossClient();
        String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
        String encodedPolicy = BinaryUtil.toBase64String(postPolicy.getBytes(StandardCharsets.UTF_8));
        String postSignature = ossClient.calculatePostSignature(postPolicy);
        ossClient.shutdown();

        //回调信息设置
        JSONObject jasonCallback = new JSONObject();
        jasonCallback.put("callbackUrl", callbackUrl);
        jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}");
        jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
        String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());

        Map<String, String> map = new HashMap<>();
        map.put("accessId", accessKeyId);
        map.put("dir", path);
        map.put("policy", encodedPolicy);
        map.put("signature", postSignature);
        map.put("host", host);
        map.put("expire", String.valueOf(expireEndTime / 1000));
        map.put("callback", base64CallbackBody);
        return map;
    }

    @Override
    public Map<String, String> getPolicy(String path) {

        //policy 过期时间为 30分钟
        long expireTime = 1800;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);
        PolicyConditions policyConditions = new PolicyConditions();
        policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 5368709120L);
        policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path);

        OSS ossClient = ossClient();
        String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
        String encodedPolicy = BinaryUtil.toBase64String(postPolicy.getBytes(StandardCharsets.UTF_8));
        String postSignature = ossClient.calculatePostSignature(postPolicy);
        ossClient.shutdown();

        Map<String, String> map = new HashMap<>();
        map.put("accessId", accessKeyId);
        map.put("policy", encodedPolicy);
        map.put("dir", path);
        map.put("signature", postSignature);
        map.put("host", "https://".concat(bucket).concat(".").concat(endpoint));
        map.put("expire", String.valueOf(expireEndTime / 1000));

        return map;
    }

    @Override
    public String getAuthFileUrl(String path) {
        OSS ossClient = ossClient();
        long expireTime = 1800;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);
        URL url = ossClient.generatePresignedUrl(bucket, path, expiration);
        ossClient.shutdown();
        return host + url.getPath() + "?" + url.getQuery();
    }

    @Override
    public String getPath(String url) {
        return url.replace(host + "/", "");
    }

    @Override
    public String parseUrl(String path) {
        return host + "/" + path;
    }

    @Override
    public String parsOssUrl(String key) {
        OSS ossClient = ossClient();
        try {
            // 设置签名URL过期时间，单位为毫秒。
            Date expiration = new Date(new Date().getTime() + 3600 * 1000);
            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            URL url = ossClient.generatePresignedUrl(bucket, key, expiration);
            String query = url.getQuery();
            return query;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }

    @Override
    public void rename(String key, String newName) {
        OSS ossClient = ossClient();
        CopyObjectRequest request = new CopyObjectRequest(bucket, key, bucket, key);
        ObjectMetadata metadata = ossClient.getObjectMetadata(bucket, key);
        metadata.setContentDisposition("attachment;filename=" + newName);
        request.setNewObjectMetadata(metadata);
        ossClient.copyObject(request);
        ossClient.shutdown();
    }
}
